package com.atguigu.aops;

import com.atguigu.annotations.MyRedisCache;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component
@Aspect
@Slf4j
public class MyRedisCacheAspect {
    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    @Pointcut("@annotation(com.atguigu.annotations.MyRedisCache)")
    public void pc(){};

    /**
 * AOP环绕通知方法，用于实现基于Redis的缓存拦截逻辑
 * 在目标方法执行前后进行拦截，实现缓存读取和写入功能
 *
 * @param proceedingJoinPoint AOP连接点对象，提供了对目标方法的访问和执行能力
 * @return 目标方法的执行结果或者缓存中的数据
 * @throws Throwable 目标方法执行过程中可能抛出的任何异常
 */
@Around("pc()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    // 声明一个Object类型的变量result用于存储方法执行结果或缓存数据，初始化为null
    Object result=null;

    // 使用try-catch块捕获可能出现的异常
    try {
        // 将连接点的签名信息强制转换为MethodSignature类型，获取方法签名信息
        MethodSignature signature =(MethodSignature)proceedingJoinPoint.getSignature();
        // 通过方法签名获取目标方法的Method对象，包含方法的所有元数据信息
        Method method = signature.getMethod();

        // 从目标方法上获取MyRedisCache注解实例，用于读取缓存配置信息
        MyRedisCache myRedisCache = method.getAnnotation(MyRedisCache.class);

        // 从注解中获取key的前缀部分，用于构建完整的Redis key
        String keyPrefix = myRedisCache.keyPrefix();
        // 从注解中获取用于生成key的SpEL表达式字符串
        String matchValue = myRedisCache.matchValue();

        // 创建SpEL表达式解析器实例，用于解析Spring表达式语言
        ExpressionParser parser = new SpelExpressionParser();
        // 使用解析器将matchValue字符串解析为Expression表达式对象
        Expression expression= parser.parseExpression(matchValue);
        // 创建标准的表达式求值上下文，用于在表达式求值时提供变量和上下文信息
        EvaluationContext context=new StandardEvaluationContext();

        // 获取目标方法的实际参数值数组，用于后续的表达式变量设置
        Object[] args = proceedingJoinPoint.getArgs();
        // 创建默认参数名发现器，用于获取方法参数的名称
        DefaultParameterNameDiscoverer discoverer=new DefaultParameterNameDiscoverer();
        // 通过参数名发现器获取方法参数名称数组
        String[] parameterNames=discoverer.getParameterNames(method);
        // 遍历所有参数名称，将参数名和对应的参数值设置到表达式上下文中
        for (int i = 0; i < parameterNames.length; i++) {
            // 打印参数名和参数值到控制台，用于调试
            System.out.println(parameterNames[i]+"\t"+args[i].toString());
            // 将参数名作为变量名，参数值作为变量值设置到表达式上下文中
            context.setVariable(parameterNames[i],args[i]);
        }

        // 构造完整的Redis key：将key前缀和表达式求值结果用冒号连接
        String key=keyPrefix+":"+expression.getValue(context).toString();
        // 打印构造的Redis key到控制台，用于调试和监控
        System.out.println("------redis 查询 key: " + key);

        // 使用RedisTemplate操作Redis，通过opsForValue()获取字符串操作接口，执行get操作查询缓存
        result=redisTemplate.opsForValue().get(key);
        // 判断从Redis中获取的结果是否不为null，即缓存命中
        if(result!=null){
            // 打印缓存命中信息到控制台，表示直接从缓存返回结果
            System.out.println("------redis里面有，我直接返回结果不再打扰mysql: " + result);
            // 直接返回缓存中的数据，不再执行目标方法，提高响应速度
            return result;
        }

        // 缓存未命中，执行目标方法获取实际数据
        result=proceedingJoinPoint.proceed();

        // 判断目标方法执行结果是否不为null，避免缓存空值
        if(result!=null){
            // 将目标方法的执行结果存入Redis缓存中，下次相同请求可直接从缓存获取
            redisTemplate.opsForValue().set(key,result);
            // 打印缓存写入信息到控制台，表示已将结果保存到Redis
            System.out.println("------redis里面没有，我直接返回结果并保存到redis: " + result);
            // 返回目标方法的执行结果
            return result;  // 添加返回语句
        }
    } catch (Throwable e) {
        // 捕获执行过程中可能出现的任何异常，并打印异常堆栈信息
        e.printStackTrace();
    }
    // 如果出现异常或者方法执行结果为null，则返回result变量的当前值(null)
    return result;
}

}
